﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;


namespace Scheduler
{
    /**Class: StaffReqForm
     * @author Sara Tuohy
     * @version 1.0
     * Course : ITEC 4860 Spring, 2012
     * Written: March, 2012
     * This class gives access to Business Operating Parameters. 
     * This form allows Managers to set Hours of Operation, Maximum Payroll,
     * and number of staff required for each time segment of operation (to be used
     * by the schedule generator algorithm).
     * Note: Functionality not yet coded: need to save to (and restore from) the DB the following data:
     * MaxPayroll, NumberOfEmployeesPerTimeSlot
     * Note: Updated to move DB read logic to the business logic layer, where it can be accessed by this (staffing)
     * panel or the employee preferences on a first come, first served basis.
     */

    public partial class StaffReqForm : Form
    {
        //Several components are shared by many of the functions and 
        //so are declared globally within the class
        /**
         * The main panel allows to return when this panel closes          */
        private ManagerMain mainPanel;
        /**
         * maxHours will be saved to the Company table                      */
        private decimal maxWeeklyPayroll;
        /**
         * Used in various places - day format matches DB                   */
        private string[] weekDays = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
        /**
         * The operation days are saved in operationWeek                    */
        public OperationDay[] operationWeek;
        /**
         * Hope this doesn't change any time soon                           */
        private const int NUM_WEEK_DAYS = 7;
        /**
         * Company ID is a needed key for data base saves                   */
        private string Co_ID = Presentation_Layer.GlobalVariables.GlobalCompanyID;
        /**
         * the various ComboBoxes are put in Arrays to arrange for efficient handling            */
        private ComboBox[] startTimes;
        private ComboBox[] endTimes;
        private Button[] setEmpNums;
        /**
         * Reusable table adapter for saving to the data Base           */
        Data_Access_Layer.StaffingRequirements staffReqData;
        /**
         * Current State - if no changes, take no action                */
        private int[] selectedIndices;

        /** 
         * Used in save logic                                           */
        private bool comboBoxUpdated;
        /**
         * Used to convert button names to day name index               */
        string[] daysOfWeek = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
        /**
         * allTimeSlots is the range of HoursOfOperation ComboBoxes     */
        string[] allTimeSlots;

        /**Method: StaffRegForm
         * Constructor for the ManagerMain panel
         * Sets up the panel for use
         * Hides the calling panel and calls setup();
         * @param ManagerMain - calling panel to hide
         */
        public StaffReqForm(ManagerMain mp)
        {
            InitializeComponent();
            operationWeek = new OperationDay[NUM_WEEK_DAYS];
            allTimeSlots = Presentation_Layer.GlobalVariables.GlobalAllTimeSlots;
            maxWeeklyPayroll = 0;
            mainPanel = mp;
            mainPanel.Hide();
            setup();
            selectedIndices = new int[NUM_WEEK_DAYS * 2];   //7 each for start and end times
            saveCurrentSettings();
        }

        /**Method: setup()
         * Sets up the ComboBoxes for use
         * Instantiates the arrays and the 'hoursOfOperationSaved' bool
         */
        private void setup()
        {

            startTimes = new ComboBox[NUM_WEEK_DAYS];
            startTimes[0] = staffReqSunStartTimeComboBox;
            startTimes[1] = staffReqMonStartTimeComboBox;
            startTimes[2] = staffReqTueStartTimeComboBox;
            startTimes[3] = staffReqWedStartTimeComboBox;
            startTimes[4] = staffReqThuStartTimeComboBox;
            startTimes[5] = staffReqFriStartTimeComboBox;
            startTimes[6] = staffReqSatStartTimeComboBox;

            endTimes = new ComboBox[NUM_WEEK_DAYS];
            endTimes[0] = staffReqSunEndTimeComboBox;
            endTimes[1] = staffReqMonEndTimeComboBox;
            endTimes[2] = staffReqTueEndTimeComboBox;
            endTimes[3] = staffReqWedEndTimeComboBox;
            endTimes[4] = staffReqThuEndTimeComboBox;
            endTimes[5] = staffReqFriEndTimeComboBox;
            endTimes[6] = staffReqSatEndTimeComboBox;

            setEmpNums = new Button[NUM_WEEK_DAYS];
            setEmpNums[0] = setSunNumsButton;
            setEmpNums[1] = setMonNumsButton;
            setEmpNums[2] = setTueNumsButton;
            setEmpNums[3] = setWedNumsButton;
            setEmpNums[4] = setThuNumsButton;
            setEmpNums[5] = setFriNumsButton;
            setEmpNums[6] = setSatNumsButton;

            loadScheduleInfo();

        }

        /**Method: loadScheduleInfo
         * Use common code to load schedule information into operationWeek
         */
        private void loadScheduleInfo()
        {
            if (!Presentation_Layer.GlobalVariables.GlobalHoursOfOperationSet)
            {
                Business_Logic_Layer.StaffingRequirements staffingData = new Business_Logic_Layer.StaffingRequirements();
                staffingData.loadDBData();
            }
            operationWeek = Presentation_Layer.GlobalVariables.GlobalOperationWeek;
            setUpGUI();
        }

        /**Method: setUpGUI
         * Set payroll and controls values
         */
        private void setUpGUI()
        {
            //set the payroll text box
            decimal weeklyPayroll = Presentation_Layer.GlobalVariables.GlobalCompanyMaxWeeklyPayroll;
            staffReqPayrollTextBox.Text = String.Format("{0:0.00}", weeklyPayroll);
            for (int i = 0; i < NUM_WEEK_DAYS; i++)
            {
                setGuiControls(i);
            }
        }

        /**Method: setGuiControls
         * Take the data restored from the database to set up the panel
         * @param int idx index of day to be addressed
         */
        private void setGuiControls(int idx)
        {
            if (operationWeek[idx].isOpen())
            {
                string strStart = operationWeek[idx].getStart().ToString();
                string strEnd = operationWeek[idx].getEnd().ToString();
                startTimes[idx].SelectedIndex = Array.IndexOf(allTimeSlots, strStart);
                endTimes[idx].SelectedIndex = Array.IndexOf(allTimeSlots, strEnd);

                setEmpNums[idx].Enabled = true;          //enable staff requirements for this day
            }                       //end if !closed
        }

        /**Method: loadDBData
         * Read data from the DB and loop through setting up times, 
         * Staff needed, and payroll
         */
        private void loadDBData()
        {   //Set payroll
            staffReqData = new Data_Access_Layer.StaffingRequirements("Sunday", Co_ID);
            if (staffReqData.locateCompany())
            {
                decimal weeklyPayroll = staffReqData.getMaxWeeklyPayroll();
                staffReqPayrollTextBox.Text = String.Format("{0:0.00}", weeklyPayroll);
            }

            for (int i = 0; i < NUM_WEEK_DAYS; i++)
            {
                staffReqData = new Data_Access_Layer.StaffingRequirements(weekDays[i], Co_ID);
                setStartEndTimes(staffReqData, i);
                if (operationWeek[i].isOpen())
                {
                    setStaffRequiredFromDB(staffReqData, i);
                }
            }
        }

        /**Method: setStartEndTimes
         * Read stored values and set values in GUI
         * Purpose: Retrieve and display stored data and
         * Data base is only accessed one time (unless modified)
         * @param D_A_L.StaffReq reused DB table accessor
         * @param int dayIdx index of day we are reading
         */
        private void setStartEndTimes(Data_Access_Layer.StaffingRequirements srd, int dayIdx)
        {
            Data_Access_Layer.StaffingRequirements staffRequirementsData = srd;
            string strStart, strEnd;

            if (staffRequirementsData.locateOperatingDay())
            {
                if (!staffReqData.getIsClosed())
                {
                    strStart = staffRequirementsData.getStartTime().ToString();
                    strEnd = staffRequirementsData.getEndTime().ToString();
                    strStart = strStart.Substring(0, 5);        //change "10:30:00" to "10:30"
                    strEnd = strEnd.Substring(0, 5);
                    startTimes[dayIdx].SelectedIndex = Array.IndexOf(allTimeSlots, strStart);
                    endTimes[dayIdx].SelectedIndex = Array.IndexOf(allTimeSlots, strEnd);
                    operationWeek[dayIdx] = new OperationDay(weekDays[dayIdx], strStart, strEnd,
                        Array.IndexOf(allTimeSlots, strStart), Array.IndexOf(allTimeSlots, strEnd));
                }                       //end if !closed
                else
                {
                    operationWeek[dayIdx] = new OperationDay(weekDays[dayIdx], "00:00", "00:00", -1, -1);
                }
            }                           //end if day located
            else
            {
                //should never, ever, EVER happen ...
                MessageBox.Show("Locate failed for " + weekDays[dayIdx]);
            }
            Scheduler.Presentation_Layer.GlobalVariables.GlobalOperationWeek = operationWeek;
            Scheduler.Presentation_Layer.GlobalVariables.GlobalHoursOfOperationSet = true;
        }

        /**Method: setStaffRequired
         * Read stored values and set values in GUI
         * Purpose: Retrieve and display stored data
         * Database is only accessed one time (unless modified)
         * Note: only called for days the business is Open
         * @param D_A_L.StaffReq reused DB table accessor
         * @param int dayIdx index of day we are reading
         */
        private void setStaffRequiredFromDB(Data_Access_Layer.StaffingRequirements srd, int dayIdx)
        {
            Array numStaffRequiredFromDB;
            object[] objArray = new object[49];
            int[] numReqPerAllTimeSlots = new int[48];
            Data_Access_Layer.StaffingRequirements staffRequirementsData = srd;
            if (staffRequirementsData.locateStaffingRequirements())
            {
                bool conversionFailed = false;
                numStaffRequiredFromDB = staffRequirementsData.getAllPerHr();
                numStaffRequiredFromDB.CopyTo(objArray, 0);

                for (int i = 0; i < numReqPerAllTimeSlots.Length; i++)
                {
                    try
                    {
                        int num = Convert.ToInt32(objArray[i + 1].ToString());
                        numReqPerAllTimeSlots[i] = num;
                    }
                    catch
                    {
                        int idx = i + 1;
                        MessageBox.Show("Conversion failed for " + objArray[0].ToString() +
                            " object number " + idx + " is: " + objArray[i + 1].ToString(),
                            "Conversion Failure on Staff Requirements from DB", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        conversionFailed = true;
                    }
                }
                if (!conversionFailed)
                {
                    operationWeek[dayIdx].setNumPerAllTimeSlots(numReqPerAllTimeSlots);
                    operationWeek[dayIdx].copyToOpenTimeSlots();
                    setEmpNums[dayIdx].Enabled = true;
                }

            }                           //end if day located
            else MessageBox.Show("Locate failed for Staffing Requirements for " + weekDays[dayIdx]);
        }

        /**Method: saveCurrentSettings
         * Saves current start and end time settings
         * Purpose: on "Save", determine if settings have changed.
         */
        private void saveCurrentSettings()
        {
            for (int i = 0; i < NUM_WEEK_DAYS; i++)
            {
                selectedIndices[i] = startTimes[i].SelectedIndex;
                selectedIndices[i + NUM_WEEK_DAYS] = endTimes[i].SelectedIndex;
            }
        }

        /**Method: setEmpNumsButton_Click
         * Sets the number of employees needed for each time slot of operation
         * The ranges conform to the Hours of Operation, so the manager must save those
         * before this method can be successful.  Numbers will be saved to the DB.
         */
        private void setEmpNumsButton_Click(object sender, EventArgs e)
        {
            //we use sender to identify the button that called this function, then equate that
            //with a day.  We use that to get the index to the operationWeek, and pull the start/end
            //time information for that day.   
            Button dayButton = (Button)sender;
            string day = dayButton.Name.Substring(3, 3).ToUpper();
            int dayNum = Array.IndexOf(daysOfWeek, day);
            OperationDay thisOperatingDay = operationWeek[dayNum];
            int startIndex = thisOperatingDay.getStartIndex();
            int arrayLength = (thisOperatingDay.getEndIndex() - thisOperatingDay.getStartIndex());
            arrayLength++; //add the label for the end of the tour ...
            string[] openHours = new string[arrayLength];
            Array.Copy(allTimeSlots, startIndex, openHours, 0, arrayLength);
            NumPerHour numPerHourPanel = new NumPerHour(openHours, thisOperatingDay);
            //The collected information is passed to a new panel.
            numPerHourPanel.ShowDialog();
            
        }

        //TODO: check if anything changed before embarking on "SAVE"    - performance concern
        /**Method: staffReqSaveButton_Click
         * Validates the data input by the manager
         * Saves the data when all is well.
         */
        private void staffReqSaveButton_Click(object sender, EventArgs e)
        {
            bool saveCanKeepGoing = checkMaxPayroll();
            if (!comboBoxUpdated)
            {
                MessageBox.Show("Cannot save until shift times are updated.", "Error: No Start/End Times Set",
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
                saveCanKeepGoing = false;
            }

            if (!startOrEndTimesUpdated())
            {
                if (saveCanKeepGoing)
                {
                    MessageBox.Show("Payroll Saved (No changes detected in Start/End Times)", "Payroll Save Result",
                        MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            else
            {
                if (saveCanKeepGoing)
                {
                    saveCanKeepGoing = checkAllDayEntries();
                }
                //if saveCanKeepGoing, all days have passed edits; save to DB.
                if (saveCanKeepGoing)
                {
                    Scheduler.Presentation_Layer.GlobalVariables.GlobalHoursOfOperationSet = true;
                    Scheduler.Presentation_Layer.GlobalVariables.GlobalOperationWeek = operationWeek;
                    saveToOpDayTable();
                    MessageBox.Show("The data have been saved.");
                }
            }
        }               //end of SaveButton_Click event


        /**Method: checkAllDayEntries
         * Cycles through the work week validating variables before allocating an "Operation Day"
         * Note: index = -1 means no selection made (ever).  Index = 0 was set to blanks to allow
         * manager to reset a day to "closed" if he updated accidentally.
         * Note: longer than 30 lines but coherent - it's one "for (i=0 to i=7)" loop.
         * @return bool isOk - true if all data are valid
         */
        private bool checkAllDayEntries()
        {
            bool isOk = true;
            string errMsg;  //to format information in MessageBoxes
            for (int i = 0; i < NUM_WEEK_DAYS; i++)
            {
                if (startOrEndTimeUpdated(i))
                {
                    if ((endTimes[i].SelectedIndex > 0) && (startTimes[i].SelectedIndex <= 0))
                    {
                        MessageBox.Show("End Time but no Start Time: " + daysOfWeek[i], "No Start Time",
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return false;
                    }
                    else if ((startTimes[i].SelectedIndex > 0) && (endTimes[i].SelectedIndex <= 0))
                    {
                        errMsg = "Start Time but no End Time: " + daysOfWeek[i] + ".";
                        MessageBox.Show(errMsg, "No End Time", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return false;
                    }
                    else if ((startTimes[i].SelectedIndex > 0) &&
                            (startTimes[i].SelectedIndex >= endTimes[i].SelectedIndex))
                    {
                        errMsg = "End time must be later than Start! See " + daysOfWeek[i] + ".";
                        MessageBox.Show(errMsg, "Start/End Conflict", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return false;
                    }
                    //allocate a new OperationDay for days the business is open
                    else if ((startTimes[i].SelectedIndex > 0) &&
                            (startTimes[i].SelectedIndex < endTimes[i].SelectedIndex))
                    {
                        operationWeek[i] = new OperationDay(weekDays[i], startTimes[i].Text, endTimes[i].Text,
                            startTimes[i].SelectedIndex, endTimes[i].SelectedIndex);
                        selectedIndices[i] = startTimes[i].SelectedIndex;
                        selectedIndices[i + NUM_WEEK_DAYS] = endTimes[i].SelectedIndex;
                        setEmpNums[i].Enabled = true;
                    }
                    //this will allocate a new closed operation day and add to operationWeek
                    else if ((startTimes[i].SelectedIndex <= 0) &&
                            (endTimes[i].SelectedIndex <= 0))
                    {
                        operationWeek[i] = new OperationDay(weekDays[i], startTimes[i].Text, endTimes[i].Text,
                            -1, -1);        //the indices need to be equal for "isClosed" to evaluate correctly ...  
                        setEmpNums[i].Enabled = false;
                    }

                }       //end if updated
            }          //end for i = 0 to NUM_WEEK_DAYS
            return isOk;
        }

        /**Method: startOrEndTimesUpdated
         * compare current selections to original settings
         * @return bool true if updated
         */
        private bool startOrEndTimesUpdated()
        {
            for (int i = 0; i < NUM_WEEK_DAYS; i++)
            {
                if (selectedIndices[i] != startTimes[i].SelectedIndex)
                    return true;
                if (selectedIndices[i + NUM_WEEK_DAYS] != endTimes[i].SelectedIndex)
                    return true;
            }
            return false;
        }

        /**Method: startOrEndTimeUpdated
         * compare current selection to original setting
         * @param int day = index of day to check (Sunday = 0, ... , Saturday = 6)
         * @return bool true if updated
         */
        private bool startOrEndTimeUpdated(int day)
        {

            if (selectedIndices[day] != startTimes[day].SelectedIndex)
                return true;
            if (selectedIndices[day + NUM_WEEK_DAYS] != endTimes[day].SelectedIndex)
                return true;

            return false;
        }

        /**Method: checkMaxPayroll
         * Validates the MaximumPayroll Field
         * Purpose: ensure the valued in the 
         * maxPayroll field will convert to 
         * decimal for saving to the DB
         * @return bool true if the data is valid
         */
        private bool checkMaxPayroll()
        {
            bool isOk = true;
            decimal weeklyPayroll;
            try
            {
                weeklyPayroll = System.Convert.ToDecimal(staffReqPayrollTextBox.Text);
                maxWeeklyPayroll = weeklyPayroll;
                staffReqData = new Data_Access_Layer.StaffingRequirements("Sunday", Co_ID);
                if (staffReqData.locateCompany())
                {
                    staffReqData.setMaxWeeklyPayroll(maxWeeklyPayroll);
                    // MessageBox.Show("Saving Max Payroll = " + maxWeeklyPayroll.ToString());
                }
            }
            catch
            {
                MessageBox.Show("Cannot save Payroll, Convert to Decimal Failed: " + staffReqPayrollTextBox.Text,
                    "Payroll Save Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                isOk = false;
            }
            return isOk;
        }


        /**Method: saveToOpDayTable()
         * Saves the data to the table
         * Save the data to be retrieved in the future
         * Note: with white space and diagnostic MessageBoxes it exceeds the 30 line limit
         * but was not refactored to maintain coherence.
         */
        private void saveToOpDayTable()
        {
            string day, strStart, strEnd;
            string currentDateTime = Presentation_Layer.GlobalVariables.GlobalCurrentDateTime;
            TimeSpan startTime, endTime;
            //DateTime zeroDate = Presentation_Layer.GlobalVariables.GlobalZeroDate;
            for (int i = 0; i < NUM_WEEK_DAYS; i++)
            {
                if (operationWeek[i].isOpen())
                {
                    day = weekDays[i];
                    staffReqData = new Data_Access_Layer.StaffingRequirements(day, Co_ID);
                    if (staffReqData.locateOperatingDay())
                    {
                        strStart = operationWeek[i].getStart();
                        startTime = convertStringToTimeSpan(strStart);
                        staffReqData.setStartTime(startTime);

                        strEnd = operationWeek[i].getEnd();
                        endTime = convertStringToTimeSpan(strEnd);
                        staffReqData.setEndTime(endTime);
                        staffReqData.setTimeChanged(currentDateTime); 
                        staffReqData.setIsClosed(false);
                    }
                    else MessageBox.Show("Locate failed for " + day);
                }
                else
                {
                    day = weekDays[i];
                    staffReqData = new Data_Access_Layer.StaffingRequirements(day, Co_ID);
                    if (staffReqData.locateOperatingDay())
                    {
                        strStart = "00:00";                             //might be " " from text boxes; hardcode it here.
                        startTime = convertStringToTimeSpan(strStart);
                        staffReqData.setStartTime(startTime);

                        strEnd = "00:00";                              //might be " " from text boxes; hardcode it here.
                        endTime = convertStringToTimeSpan(strEnd);
                        staffReqData.setEndTime(endTime);
                        //staffReqData.setTimeChanged(formatNow());
                        staffReqData.setTimeChanged(currentDateTime);
                        staffReqData.setIsClosed(true);
                    }
                    else MessageBox.Show("Locate failed for " + day);
                }
            }
        }

        /**Method: convertStringToTimeSpan
         * Changes text to TimeSpan data type for storage in DB
         * We know the text is valid because it came from 
         * uneditable text boxes.  Substring and convert to Int32.
         */
        private TimeSpan convertStringToTimeSpan(string strTime)
        {
            TimeSpan tSpan;
            int hr, min;
            int colon = strTime.IndexOf(":");
            string strHr = strTime.Substring(0, colon);
            string strMin = strTime.Substring(colon + 1);
            hr = Convert.ToInt32(strHr);
            min = Convert.ToInt32(strMin);
            tSpan = new TimeSpan(hr, min, 0);
            return tSpan;
        }


        /**Method: staffReqHomeButton_Click
         * Return to calling panel. 
         */
        private void staffReqHomeButton_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /**Method: staffReqGenerateSchedule_Click
         * Start generation of all possible schedules and has them scored to commit the best
         * schedule to DB as shifts
         * 
         * Added by Victor. 
         * Updated 04/17/2012 to call ScheduleDateSelector dialog box to allow user to 
         * select the date for which he would like the schedule generated.          sft
         */
        private void staffReqGenerateSchedule_Click(object sender, EventArgs e)
        {
            ScheduleDateSelector sds = new ScheduleDateSelector();
            if (sds.ShowDialog(this) == DialogResult.OK)
            {
                if (sds.dateWasSelected())
                {
                    string selectedDate = sds.scheduleDateTimePicker.Text;
                    MessageBox.Show("Generating Schedule for " + selectedDate);
                    Business_Logic_Layer.GenerateAllPossibleSchedules s = 
                        new Business_Logic_Layer.GenerateAllPossibleSchedules(selectedDate);
                    MessageBox.Show("Schedule Generated.");
                    sds.Dispose();
                }
                else
                {
                    MessageBox.Show("No valid date selected, generate schedule request terminated.");
                    sds.Dispose();
                }
            }
            else
            {
                MessageBox.Show("Generate Schedule Request Cancelled");
                sds.Dispose();
            }
        }

        /**Method: OnFormClosing
         * Return to calling panel
         */
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            mainPanel.Show();
        }

        /**Method: StaffReqForm_Load
         * This was causing problems.  Moved all functionality
         * to setUp().
         */
        private void StaffReqForm_Load(object sender, EventArgs e)
        {

        }

        /**Method: shiftTimeComboBox_SelectedIndexChanged
         * If any Start or End Time combo box is updated, enable the "Save" button
         * To allow manager to save updates. 
         */
        private void shiftTimeComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            ComboBox cb = (ComboBox)sender;
            if (!(cb.SelectedIndex == 0))
            {
                comboBoxUpdated = true;
                staffReqSaveButton.Enabled = true;
            }
        }

        /**Method: updateFeedback
         * Augment the feedback message
         * Purpose: provide meaningful feedback to the user
         */
        private string updateFeedback(int dayIdx, string msg, int len)
        {
            string updatedMsg = msg;
            if (msg.Length > len)
            {
                updatedMsg += ", " + weekDays[dayIdx];
            }
            else
            {
                updatedMsg += weekDays[dayIdx];
            }
            return updatedMsg;
        }

    }                                   //end of StaffingRequirements class

}
